package com.nutquest.server;

import java.io.*;
import java.net.*;
import java.util.*;
import com.nutquest.common.dataobjects.Player;
import com.nutquest.common.dataobjects.Player.PlayerState;
import com.nutquest.common.dataobjects.Terrain;
import com.nutquest.common.mapdata.MapOne;

/**
 * The Class chatServer.
 */
public class GameServer implements Ports {

	/** The clients. */
	static Vector clients;

	static MapOne map = new MapOne();

	public static final String SEND_TERRAIN_COMMAND = "Terrain";

	static Vector<Player> players = new Vector<Player>();

	/** The client socket. */
	static Socket clientSocket;

	public static final String MSG_LOGIN = "Login";

	public static final String MSG_STARTING_GAME = "StartingGame";

	public static final String MSG_KEY_LEFT = "left";
	public static final String MSG_KEY_RIGHT = "right";
	public static final String MSG_KEY_DOWN = "down";
	public static final String MSG_KEY_UP = "up";

	public static final String MSG_LOGOUT = "Logout";

	/**
	 * The main method.
	 * 
	 * @param args
	 *            the arguments
	 */
	public static void main(String args[]) {

		try {
			System.out.println("getLocalHost: " + InetAddress.getLocalHost());
			System.out.println("getByName:    "
					+ InetAddress.getByName("localhost"));
			// new mainServer(); // run game server

		} catch (UnknownHostException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		clients = new Vector();

		clientSocket = null;

		// server socket
		ServerSocket serverSocket = null;

		try {
			serverSocket = new ServerSocket(PORT);
		} catch (IOException e) {
			System.out.println("IO " + e);
		}

		while (true) {
			try {

				System.out.println("Chat server running ");
				System.out.println("Waiting for the clients to connect...");

				clientSocket = serverSocket.accept();

				cThread s = new cThread(clientSocket);

				clients.add(s);
				s.start();
			} catch (IOException e) {
				System.out.println("IOaccept " + e);

			}
		}

	}

	/**
	 * The Class cThread.
	 */
	public static class cThread extends Thread {
		Player player;

		/** The nick. */
		String nick;

		/** The connected. */
		Boolean connected;

		/** The socket. */
		Socket socket;

		/** The out. */
		// PrintWriter out;

		/** The in. */
		ObjectInputStream in;

		/** The client socket. */
		Socket clientSocket;

		ObjectOutputStream out;

		/**
		 * Instantiates a new c thread.
		 * 
		 * @param s
		 *            the s
		 */
		cThread(Socket s) {
			super("cThread");

			connected = false;

			nick = "";

			// constructor
			clientSocket = s;
			try {

				out = new ObjectOutputStream(clientSocket.getOutputStream());
				in = new ObjectInputStream(clientSocket.getInputStream());
			} catch (Exception e) {
				System.out.println(e);
			}
		}

		/**
		 * Equals.
		 * 
		 * @param c
		 *            the c
		 * @return true, if successful
		 */
		public boolean equals(cThread c) {
			return (c.nick.equals(this.nick));
		}

		/**
		 * Send.
		 * 
		 * @param msg
		 *            the message
		 */
		synchronized void send(String msg) {
			try {
				out.reset();
				out.writeObject(msg);

				if (msg == "Players") {
					out.writeObject(players);
				}
				
				if(msg == "Map")
				{
					out.writeObject(GameServer.map);
				}

				out.flush();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}

		synchronized void send(String str, Object obj) {
			try {
				if (str != null) {
					if (obj != null) {
						out.reset();
						out.writeObject(str);
						out.writeObject(obj);
						out.flush();
					}
				}

			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}


		/**
		 * Listen.
		 */
		void listen() {
			try {

				while (true) {
					String msg = null;

					try {
						msg = (String) in.readObject();
					} catch (Exception e) {
						System.out.println(e);
					}
					System.out.println(msg);

					if (msg.startsWith(MSG_LOGIN)) {
						login(msg);
					}

					else if (msg.startsWith(MSG_KEY_LEFT)) {
						updatePlayerCoords(-1, 0);
					}

					else if (msg.startsWith(MSG_KEY_RIGHT)) {
						updatePlayerCoords(1, 0);
					}

					else if (msg.startsWith(MSG_KEY_DOWN)) {
						updatePlayerCoords(0, 1);
					}

					else if (msg.startsWith(MSG_KEY_UP)) {
						updatePlayerCoords(0, -1);
					}

					else if (msg.startsWith(MSG_STARTING_GAME)) {
						for (int i = 0; i < players.size(); i++) {
							if (player.getName() == players.get(i).getName()) {
								players.get(i).setState(PlayerState.FORAGING);
								sendPlayers();
								break;
							}
						}
					}

					else if (msg.equals(MSG_LOGOUT)) {
						if (logout()) {
							in.close();
							clientSocket.close();

							return;
						}
					}

					else if (msg.startsWith("Post ")) {

						for (int i = 0; i < GameServer.clients.size(); i++) {
							cThread t = (cThread) GameServer.clients.get(i);
							if (t.connected)

							{
								t.send("Receive " + nick + ": "
										+ msg.substring(5, msg.length()));
							}
						}
					}

					// Private post
					else if (msg.startsWith("PrivatePost ")) {

						StringTokenizer st = new StringTokenizer(msg.substring(
								12, msg.length()), ", ");

						String message = st.nextToken();
						String to = st.nextToken();

						boolean success = false;

						for (int i = 0; i < GameServer.clients.size(); i++) {
							cThread t = (cThread) GameServer.clients.get(i);
							if (t.nick.equals(to)) {
								t.send("Private Receive " + nick + ": "
										+ message);
								success = true;
								break;
							}
						}

						if (!success) {
							send("Error: ");
						}
					}

					else {
						send(msg);
					}
				}
			} catch (SocketException e) {
				System.out.println(e.getMessage());
				if (connected) {
					try {
						connected = false;
						GameServer.clients.remove(this);
						sendPlayers();
						in.close();
						clientSocket.close();
						return;
					} catch (Exception d) {
						return;
					}
				}
			} catch (Exception e) {
				System.out.println("Unknown Error: " + e);
			}
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.lang.Thread#run()
		 */
		public void run() {
			listen();
		}

		/**
		 * This method will update the player coordinates.
		 */

		void updatePlayerCoords(int leftRight, int upDown) {

			// check to see if the requested value is withing the boundaries of
			// the map.

			int requestedX = player.getX() + leftRight;
			int requestedY = player.getY() + upDown;

			// it must check for the map size and if the player is out of
			// bounds it should not do anything
			if (requestedX >= 0 && requestedX < map.getMapWidth()) {
				if (requestedY >= 0 && requestedY < map.getMapHeight()) {

					// Check for the grass if not not valid move
					Terrain requestedTerrain = map.getTerrainAt(requestedX,
							requestedY);
					if (requestedTerrain != null) {
						if (requestedTerrain.getType() == Terrain.TerrainType.GRASS) {

							boolean heroFound = false;
							Player thisPlayah = null;
							// Check to see if we can find the reference to our
							// player so
							// we can update it's coordinates.
							for (int i = 0; i < players.size(); i++) {
								thisPlayah = players.get(i);

								// Check to see if we can find the reference to
								// our player so
								// we can update it's coordinates.
								if (player.getName() == thisPlayah.getName()) {

									// update our local reference to player
									player.setX(requestedX);
									player.setY(requestedY);

									// update the player list
									thisPlayah.setX(player.getX());
									thisPlayah.setY(player.getY());

									// snatch any nuts that exist at this
									// terrain
									if (requestedTerrain.hasObjects()) {
										player.setObjects(requestedTerrain
												.getObjects());
										requestedTerrain.setObjects(null);
										sendTerrain(requestedTerrain);
									}

									// indicate that we have found the player we
									// are looking for and his coordinates are
									// updated.
									heroFound = true;

									// broadcast
									sendPlayers();
									break;
								}

							}

							// check to see if there are any collisions
							if (thisPlayah != null && heroFound) {
								// Check to see if we can find the reference to
								// our player so
								// we can update it's coordinates.
								for (int r = 0; r < players.size(); r++) {
									Player otherPlayer = players.get(r);

									// check to see if ANY other players already
									// exist in this spot
									if (hasCollision(thisPlayah, otherPlayer)) {

										// TODO There was a collision, pick a random winner and notify the parties and take the nuts if you have to. 
									}
								}
							}

						}

					}
				}

			}
		}

		/**
		 * Check for the Collision detection
		 * 
		 * @param player1
		 *            Player 1
		 * @param player2
		 * @return represents if Collision has occurred
		 */

		private boolean hasCollision(Player player1, Player player2) {
			if (player1 != null && player2 != null) {
				if (player1.getX() == player2.getX()) {

					if (player1.getY() == player2.getY()) {

						return true;

					}
				}
			}

			return false;

		}

		/**
		 * Login.
		 * 
		 * @param msg
		 *            the message
		 * @return true, if successful
		 */
		boolean login(String msg) {
			boolean exists = false;

			if (connected) {
				send("Allready Connected!");
				return true;
			}

			for (int i = 0; i < GameServer.clients.size(); i++) {
				if (GameServer.clients.get(i) != null) {

					// exists = true break
					System.out.println(msg.substring(7, msg.length()));
					cThread temp = (cThread) GameServer.clients.get(i);
					if ((temp.nick).equals(msg.substring(7, msg.length()))) {
						exists = true;
						break;
					}

				}
			}

			if (exists) {
				send("NewNick");
			} else {
				connected = true;
				nick = msg.substring(7, msg.length());
				// TODO: place players
				// int xy = players.size();

				player = map.initializePlayerCoords(new Player(nick, -1, -1));
				players.add(player);

				sendMap();
				sendPlayers();
			}
			return true;
		}

		private void sendMap() {
			send("Map");
			
		}

		boolean logout() {
			if (connected) {
				connected = false;
				GameServer.clients.remove(this);

				for (int i = 0; i < players.size(); i++) {
					if (player.getName() == players.get(i).getName()) {
						players.remove(i);
						break;
					}
				}

				sendPlayers();
				send("OK");

				return true;
			}

			else {
				send("Not Logged in !!");
			}

			return false;
		}

		void sendPlayers() {
			System.out.println(GameServer.clients.size());
			if (GameServer.clients.size() == 0) {
				return;
			}

			System.out.println("sendPlayers: " + GameServer.clients.size());

			// Send Players to all clients
			for (int i = 0; i < GameServer.clients.size(); i++) {
				cThread t = (cThread) GameServer.clients.get(i);
				if (t.connected) {
					t.send("Players");
				}
			}
		}

		void sendTerrain(Terrain terrain) {
			if (GameServer.map == null) {
				return;
			}

			System.out.println("sendTerrain: " + terrain);

			// Send a Terrain object update to all clients
			for (int i = 0; i < GameServer.clients.size(); i++) {
				cThread t = (cThread) GameServer.clients.get(i);
				if (t.connected) {
					t.send(SEND_TERRAIN_COMMAND, terrain);
				}
			}
		}
	}

}
